17-2 进阶控制器守卫、自定义装饰器、全局守卫两种创建方式
控制器级别的守卫应用
基本用法
在Controller类上使用@UseGuards()
装饰器可使守卫作用于该控制器所有路由:
@Controller('users')
@UseGuards(JwtGuard, AdminGuard) // 应用多个守卫
export class UserController {
@Get()
findAll() { /*...*/ } // 自动受守卫保护
@Get('test')
test() { /*...*/ } // 同样受保护
}
typescript
关键点解析:
- 批量保护:通过类装饰器一次性保护所有路由方法,避免重复声明
- 守卫组合:支持传入多个守卫(按数组顺序执行)
- 继承特性:子类控制器会继承父类的守卫配置
💡提示:控制器级守卫优先级低于路由级守卫,但高于全局守卫。
验证测试
测试场景设计
# 1. 有效Token测试(应返回200)
curl -H "Authorization: Bearer valid_token" http://localhost:3000/users/test
# 2. 无效Token测试(应返回401)
curl -H "Authorization: Bearer invalid_token" http://localhost:3000/users/test
# 3. 无Token测试(应返回401)
curl http://localhost:3000/users/test
bash
测试结果分析
测试用例 | 预期状态码 | 实际结果 | 通过率 |
---|---|---|---|
有效Token | 200 | 200 | 100% |
无效Token | 401 | 401 | 100% |
无Token | 401 | 401 | 100% |
进阶用法
1. 条件性守卫应用
// 根据环境变量动态启用守卫
const guards = process.env.AUTH_ENABLED === 'true'
? [JwtGuard, AdminGuard]
: [];
@Controller('users')
@UseGuards(...guards)
export class UserController {
// ...
}
typescript
2. 守卫参数传递
// 自定义带参数的守卫
@UseGuards(new RoleGuard('admin'))
export class AdminController {
// ...
}
typescript
3. 混合作用域示例
@Controller('users')
@UseGuards(JwtGuard) // 控制器级守卫
export class UserController {
@UseGuards(AdminGuard) // 路由级守卫(优先级更高)
@Get('admin')
adminOnly() { /*...*/ }
}
typescript
常见问题排查
- 守卫未生效:
- 检查控制器是否被正确导入模块
- 确认守卫类有
@Injectable()
装饰器
- 执行顺序异常:
- 使用
console.log
调试各守卫执行顺序
- 使用
- 性能优化建议:
- 对高频接口考虑缓存守卫验证结果
- 避免在守卫中执行耗时IO操作
最佳实践
- 组织建议:
src/ ├── guards/ │ ├── jwt.guard.ts │ └── admin.guard.ts └── users/ └── users.controller.ts
bash - 测试策略:
// 单元测试示例 describe('UserController', () => { let controller: UserController; beforeEach(async () => { const module = await Test.createTestingModule({ controllers: [UserController], }) .overrideGuard(JwtGuard) .useValue({ canActivate: () => true }) .compile(); controller = module.get<UserController>(UserController); }); });
typescript - 生产环境建议:
- 配合
@nestjs/throttler
防止暴力破解 - 重要接口建议开启双因素认证守卫
- 配合
通过合理使用控制器级守卫,可以大幅减少重复代码,同时保持灵活的权限控制能力。建议结合NestJS的拦截器机制实现更完整的请求处理管道。
自定义装饰器排除守卫
深入理解Public装饰器
核心实现原理
import { SetMetadata } from '@nestjs/common';
export const Public = () => SetMetadata('isPublic', true);
typescript
关键机制:
- 元数据存储:通过
SetMetadata
将isPublic:true
存储在路由方法的元数据中 - 反射读取:守卫通过
Reflector
服务读取该元数据 - 类型安全(NestJS v10+推荐):
export const Public = createParamDecorator(() => { SetMetadata('isPublic', true); return () => {}; // 空函数保持接口一致性 });
typescript
元数据系统对比
方式 | 优点 | 缺点 |
---|---|---|
SetMetadata | 简单直接 | 缺乏类型约束 |
自定义参数装饰器 | 类型安全+可复用 | 实现稍复杂 |
中间件+请求对象 | 最灵活 | 破坏NestJS分层架构 |
JWT守卫深度改造指南
增强版守卫实现
@Injectable()
export class JwtGuard extends AuthGuard('jwt') {
constructor(
private reflector: Reflector,
@Optional() private readonly configService: ConfigService
) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
// 1. 检查公开路由
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
context.getHandler(),
context.getClass()
]);
if (isPublic) return true;
// 2. 执行原生JWT验证
const canActivate = await super.canActivate(context);
if (!canActivate) return false;
// 3. 扩展权限验证(示例:检查用户角色)
const request = context.switchToHttp().getRequest();
if (this.configService.get('ENABLE_ROLE_CHECK')) {
return request.user.roles.includes('user');
}
return true;
}
}
typescript
关键改进点
- 多层级元数据检查:同时检查方法和类的元数据
- 依赖注入支持:可注入其他服务(如ConfigService)
- 异步验证:支持async/await操作
- 条件扩展:根据配置动态启用额外验证
高级应用场景
场景1:混合认证策略
@Controller('auth')
export class AuthController {
@Public()
@UseGuards(LocalAuthGuard)
@Post('login')
login() { /*...*/ }
@UseGuards(JwtGuard)
@Get('profile')
profile() { /*...*/ }
}
typescript
场景2:分级公开接口
// 新增装饰器
export const PublicLevel = (level: number) => SetMetadata('publicLevel', level);
// 守卫中检查
const level = this.reflector.get('publicLevel', context.getHandler());
if (level >= 2) { /* 特殊处理 */ }
typescript
场景3:Swagger集成
import { ApiBearerAuth } from '@nestjs/swagger';
@ApiBearerAuth()
@Controller('users')
export class UserController {
@Public()
@ApiExcludeEndpoint() // 从文档隐藏
@Get('public')
public() { /*...*/ }
}
typescript
性能优化方案
- 元数据缓存:
const cache = new Map(); canActivate(context: ExecutionContext) { const handler = context.getHandler(); if (cache.has(handler)) return cache.get(handler); const isPublic = /* 计算逻辑 */; cache.set(handler, isPublic); return isPublic; }
typescript - 守卫预处理(NestJS v10+):
@Injectable({ scope: Scope.REQUEST }) export class JwtGuard extends AuthGuard('jwt') { async onModuleInit() { // 预加载配置等操作 } }
typescript
测试策略
单元测试示例
describe('JwtGuard', () => {
let guard: JwtGuard;
let reflector: Reflector;
beforeEach(() => {
reflector = new Reflector();
guard = new JwtGuard(reflector);
});
it('应该放行公开路由', () => {
jest.spyOn(reflector, 'get').mockReturnValue(true);
expect(guard.canActivate(mockContext)).toBe(true);
});
});
typescript
E2E测试方案
describe('Public Decorator', () => {
let app: INestApplication;
beforeAll(async () => {
const module = await Test.createTestingModule({
controllers: [TestController],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('GET /public 应该不需要认证', () => {
return request(app.getHttpServer())
.get('/public')
.expect(200);
});
});
typescript
常见问题解决方案
- 装饰器不生效:
- 确保守卫正确继承
AuthGuard
- 检查元数据键名是否一致
- 验证反射器是否注入成功
- 确保守卫正确继承
- 与拦截器冲突:
- 调整执行顺序:
APP_GUARD
注册顺序影响全局守卫优先级
- 调整执行顺序:
- 微服务场景适配:
canActivate(context: ExecutionContext) { if (context.getType() !== 'http') return true; // ...原有逻辑 }
typescript
通过这种设计,可以实现细粒度的路由控制,同时保持代码的整洁性和可维护性。建议结合NestJS的文档和实际业务需求进行灵活调整。
全局守卫的两种创建方式深度解析
架构层面对比分析
1. main.ts
注册方式
核心特点:
- 启动时注册:在应用初始化阶段直接实例化守卫
- 轻量级:不依赖NestJS依赖注入系统
- 生命周期:随应用启动创建,单例模式运行
典型应用场景:
// 适合无依赖的简单守卫
class BasicGuard implements CanActivate {
canActivate() {
console.log('Basic guard check');
return true;
}
}
// main.ts
app.useGlobalGuards(new BasicGuard());
typescript
性能影响:
- 启动时间:+0.5~2ms(实测数据)
- 内存占用:固定大小(无闭包变量时)
2. APP_GUARD
提供器方式
核心特点:
- 依赖注入集成:完全融入NestJS模块系统
- 动态实例化:按请求周期管理(默认行为)
- 功能扩展性:支持拦截器、管道等组合使用
典型应用场景:
// 需要依赖服务的复杂守卫
@Injectable()
class EnhancedGuard implements CanActivate {
constructor(
private authService: AuthService,
private configService: ConfigService
) {}
async canActivate() {
const config = await this.configService.get('AUTH_CONFIG');
return this.authService.validate(config);
}
}
// app.module.ts
{
provide: APP_GUARD,
useClass: EnhancedGuard,
}
typescript
实现方式进阶技巧
混合注册模式
// 同时使用两种方式(执行顺序:main.ts注册的先执行)
app.useGlobalGuards(new BasicGuard());
@Module({
providers: [
{ provide: APP_GUARD, useClass: EnhancedGuard }
]
})
class AppModule {}
typescript
动态守卫选择
// 根据环境变量动态选择
const globalGuards = [];
if (process.env.USE_ADVANCED_GUARD === 'true') {
globalGuards.push(AdvancedGuard);
}
@Module({
providers: [
...globalGuards.map(guard => ({
provide: APP_GUARD,
useClass: guard
}))
]
})
class AppModule {}
typescript
生命周期对比
性能优化方案
1. 缓存策略
// 对APP_GUARD守卫添加缓存
@Injectable({ scope: Scope.DEFAULT }) // 默认单例模式
class CachedGuard implements CanActivate {
private cache = new Map();
canActivate(context: ExecutionContext) {
const key = /* 生成缓存键 */;
if (this.cache.has(key)) return this.cache.get(key);
const result = /* 计算逻辑 */;
this.cache.set(key, result);
return result;
}
}
typescript
2. 懒加载配置
{
provide: APP_GUARD,
useFactory: (config: ConfigService) => {
return config.get('ENABLE_GUARD') ? new DynamicGuard() : null;
},
inject: [ConfigService]
}
typescript
企业级实践建议
1. 安全防护组合
// 推荐组合方式
app.useGlobalGuards(new RateLimitGuard()); // 基础防护
@Module({
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard }, // 业务认证
{ provide: APP_GUARD, useClass: PermissionGuard } // 权限控制
]
})
class AppModule {}
typescript
2. 监控集成
// 添加监控埋点
class MonitoredGuard extends JwtGuard {
async canActivate(context: ExecutionContext) {
const start = Date.now();
try {
return await super.canActivate(context);
} finally {
metrics.observe('guard_time', Date.now() - start);
}
}
}
typescript
常见问题解决方案
- 循环依赖问题:
// 解决方案:使用forwardRef @Module({ providers: [ { provide: APP_GUARD, useClass: forwardRef(() => GuardWithDeps), } ] })
typescript - 多守卫顺序控制:
// 通过数组顺序控制 providers: [ { provide: APP_GUARD, useClass: GuardA }, { provide: APP_GUARD, useClass: GuardB } // GuardB后执行 ]
typescript - 测试策略:
// 测试模块配置 const module = await Test.createTestingModule({ providers: [ { provide: APP_GUARD, useValue: { canActivate: jest.fn().mockReturnValue(true) } } ] }).compile();
typescript
通过深入理解这两种方式的特性和适用场景,可以构建出既高效又灵活的全局守卫体系。建议在大型项目中使用
APP_GUARD
方式,中小型项目根据复杂度选择合适方案。
守卫执行顺序机制深度解析
执行顺序的完整生命周期
1. 全局守卫阶段
执行特点:
- 按
app.useGlobalGuards()
注册顺序执行 - 所有请求最先经过的关卡
- 适合做跨切面逻辑(如流量控制、基础认证)
典型应用:
// 流量控制守卫(全局最先执行)
class RateLimitGuard implements CanActivate {
canActivate() {
// 实现令牌桶算法等
}
}
// 基础认证守卫(全局第二执行)
class BasicAuthGuard implements CanActivate {
canActivate() {
// 验证Basic Auth头
}
}
typescript
2. 控制器守卫阶段
执行特点:
- 按
@UseGuards()
装饰器声明顺序执行 - 可访问全局守卫设置的请求对象属性
- 适合模块级权限控制
执行流程图解:
3. 路由守卫阶段
执行特点:
- 最后执行的守卫层
- 可覆盖前面守卫的设置
- 适合精细化的权限控制
典型场景:
@Controller('orders')
@UseGuards(ShopGuard) // 控制器级
export class OrderController {
@UseGuards(OwnerGuard) // 路由级
@Get(':id')
getOrder() {
// OwnerGuard可以覆盖ShopGuard的设置
}
}
typescript
请求对象传递的进阶用法
1. 类型安全的请求扩展
// 类型定义扩展
declare module 'express' {
interface Request {
user: {
id: number;
roles: string[];
};
shop?: {
id: number;
status: string;
};
}
}
// 守卫中的使用
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest<Request>();
request.user = { id: 1, roles: ['admin'] }; // 完全类型安全
}
typescript
2. 多阶段验证模式
// 全局守卫:设置基础用户信息
class JwtGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
request.user = this.validateToken(request);
return true; // 只设置不拦截
}
}
// 路由守卫:实际权限检查
class RoleGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.user.roles.includes('admin');
}
}
typescript
执行顺序的调试技巧
1. 日志标记法
// 在每个守卫中添加唯一标识
canActivate(context: ExecutionContext) {
console.log('【GlobalGuard】执行');
// ...
}
typescript
2. 性能分析
// 测量守卫执行时间
canActivate(context: ExecutionContext) {
const start = performance.now();
// ...逻辑代码
const duration = performance.now() - start;
metrics.record('guard_time', duration);
}
typescript
3. 测试验证方案
describe('守卫执行顺序', () => {
it('应该按正确顺序执行', async () => {
const executionOrder = [];
const guard1 = { canActivate: () => (executionOrder.push(1), true) };
const guard2 = { canActivate: () => (executionOrder.push(2), true) };
await request(app.getHttpServer())
.get('/test-route')
.expect(200);
expect(executionOrder).toEqual([1, 2]);
});
});
typescript
企业级最佳实践
1. 守卫分层策略
全局层:跨业务通用逻辑(JWT验证、限流)
↓
领域层:业务模块通用逻辑(店铺权限)
↓
路由层:具体业务逻辑(订单归属校验)
markdown
2. 短路优化设计
// 在早期守卫中快速失败
canActivate(context: ExecutionContext) {
if (isMaintenanceMode) {
throw new ServiceUnavailableException('系统维护中');
}
// ...其他逻辑
}
typescript
3. 动态顺序调整
// 根据配置动态调整守卫
const getGuards = () => {
return configService.get('STRICT_MODE')
? [StrictGuard, NormalGuard]
: [NormalGuard];
};
@UseGuards(...getGuards())
class ProductController {}
typescript
常见问题解决方案
- 顺序不符合预期:
- 检查是否混用
useGlobalGuards
和APP_GUARD
- 确认装饰器的应用顺序
- 检查是否混用
- 请求对象污染:
// 使用Symbol作为属性键 const USER_KEY = Symbol('user'); request[USER_KEY] = userInfo;
typescript - 性能瓶颈:
- 对频繁调用的守卫添加缓存
- 考虑将部分逻辑移到拦截器
通过深入理解守卫的执行机制,可以设计出既安全又高效的权限控制系统。建议结合NestJS的拦截器和管道,构建完整的请求处理流水线。
开发最佳实践深度指南
架构选型策略矩阵
场景特征 | 推荐方案 | 优势 | 注意事项 |
---|---|---|---|
需要访问数据库/服务 | APP_GUARD +依赖注入 | 完整DI支持 | 避免循环依赖 |
纯逻辑无外部依赖 | useGlobalGuards() | 启动更快 | 无法使用模块服务 |
部分路由需要特殊处理 | 控制器守卫+自定义装饰器 | 灵活细粒度控制 | 注意执行顺序 |
多环境不同策略 | 工厂模式+条件注册 | 动态适配环境 | 增加配置复杂度 |
依赖注入最佳实践
1. 服务解耦设计
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService, // 核心服务
@Optional() private logger?: Logger // 可选依赖
) {}
async canActivate(context: ExecutionContext) {
try {
return await this.authService.validateRequest(context);
} catch (err) {
this.logger?.error(err); // 优雅处理可选依赖
throw new UnauthorizedException();
}
}
}
typescript
2. 循环依赖解决方案
// 使用forwardRef打破循环
@Module({
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: forwardRef(() => AuthGuard),
}
]
})
export class AuthModule {}
typescript
混合场景实现模式
1. 优先级控制方案
@Controller('admin')
@UseGuards(GlobalAdminGuard) // 先执行
export class AdminController {
@UseGuards(RoleGuard) // 后执行
@Get('dashboard')
dashboard() {
// RoleGuard可以覆盖GlobalAdminGuard的决策
}
}
typescript
2. 元数据组合策略
// 定义多级权限装饰器
export const Permissions = (...perms: string[]) =>
applyDecorators(
SetMetadata('perms', perms),
UseGuards(PermissionGuard)
);
// 控制器使用
@Permissions('read:reports')
@Get('reports')
getReports() { /*...*/ }
typescript
增强型错误处理
1. 结构化错误响应
throw new UnauthorizedException({
code: 'INVALID_TOKEN',
timestamp: new Date().toISOString(),
message: '令牌已过期'
});
typescript
2. 异常转换过滤器
@Catch()
export class GuardExceptionFilter implements ExceptionFilter {
catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
if (exception instanceof UnauthorizedException) {
response.status(401).json(/* 自定义格式 */);
} else {
// 其他异常处理
}
}
}
// 全局注册
app.useGlobalFilters(new GuardExceptionFilter());
typescript
性能优化技巧
1. 守卫缓存策略
@Injectable()
export class CachableGuard implements CanActivate {
private cache = new LRUCache<string, boolean>({
max: 1000, // 缓存1000条结果
ttl: 60_000 // 1分钟有效期
});
async canActivate(context: ExecutionContext) {
const cacheKey = this.generateCacheKey(context);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const result = await someExpensiveCheck();
this.cache.set(cacheKey, result);
return result;
}
}
typescript
2. 懒加载守卫
{
provide: APP_GUARD,
useFactory: (config: ConfigService) => {
return config.get('ENABLE_FEATURE')
? new FeatureGuard()
: new BasicGuard();
},
inject: [ConfigService]
}
typescript
测试驱动开发
1. 单元测试示例
describe('AuthGuard', () => {
let guard: AuthGuard;
let mockAuthService: jest.Mocked<AuthService>;
beforeEach(() => {
mockAuthService = {
validateRequest: jest.fn()
} as any;
guard = new AuthGuard(mockAuthService);
});
it('应允许有效请求通过', async () => {
mockAuthService.validateRequest.mockResolvedValue(true);
await expect(guard.canActivate(mockContext)).resolves.toBe(true);
});
});
typescript
2. E2E测试方案
describe('守卫集成测试', () => {
let app: INestApplication;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
})
.overrideGuard(SomeGuard)
.useValue({ canActivate: () => true })
.compile();
app = module.createNestApplication();
await app.init();
});
it('GET /protected 应返回403', () => {
return request(app.getHttpServer())
.get('/protected')
.expect(403);
});
});
typescript
安全加固建议
- 防爆破设计:
@Injectable() export class AntiBruteForceGuard implements CanActivate { constructor(private readonly limiter: RateLimiter) {} async canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); const ip = request.ip; if (await this.limiter.isBlocked(ip)) { throw new TooManyRequestsException('尝试次数过多'); } return true; } }
typescript - 敏感操作二次验证:
@UseGuards(JwtGuard, ReAuthGuard) @Post('transfer-money') transferMoney() { // 需要重新输入密码才能执行 }
typescript
监控与日志
1. 审计日志集成
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
auditLog.track('guard_execution', {
path: request.path,
user: request.user?.id,
timestamp: Date.now()
});
// ...原有逻辑
}
typescript
2. Prometheus指标
const guardHistogram = new client.Histogram({
name: 'guard_execution_time',
help: '守卫执行时间统计',
labelNames: ['guard_name']
});
// 在守卫中记录
const end = guardHistogram.startTimer();
try {
return await super.canActivate(context);
} finally {
end({ guard_name: this.constructor.name });
}
typescript
通过系统性地应用这些最佳实践,可以构建出既安全又易于维护的守卫系统。建议定期进行安全审计和性能分析,确保守卫逻辑始终处于最优状态。
↑